package com.stars.easyms.base.util;

import com.alibaba.fastjson.JSON;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 泛型工具类
 *
 * @author guoguifang
 * @date 2018-10-19 10:07
 * @since 1.0.0
 */
public final class GenericTypeUtil {

    /**
     * 获取指定class的泛型参数的类型
     */
    public static Class getGenericClass(Type genType, int... parameterIndexs) {
        if (parameterIndexs.length == 1) {
            return getGenericClass(genType, parameterIndexs[0]);
        }
        int parameterIndex = parameterIndexs[0];
        if (!(genType instanceof ParameterizedType)) {
            return Object.class;
        }

        Type[] params = ((ParameterizedType) genType).getActualTypeArguments();
        if (parameterIndex >= params.length || parameterIndex < 0) {
            throw new IndexOutOfBoundsException();
        }

        int[] subParameterIndexs = new int[parameterIndexs.length - 1];
        System.arraycopy(parameterIndexs, 1, subParameterIndexs, 0, parameterIndexs.length - 1);
        return getGenericClass(params[parameterIndex], subParameterIndexs);
    }

    private static Class getGenericClass(Type genType, int parameterIndex) {
        if (!(genType instanceof ParameterizedType)) {
            return Object.class;
        }

        Type[] params = ((ParameterizedType) genType).getActualTypeArguments();
        if (parameterIndex >= params.length || parameterIndex < 0) {
            throw new IndexOutOfBoundsException();
        }

        Type param = params[parameterIndex];
        if (!(param instanceof Class)) {
            if (!(param instanceof ParameterizedType)) {
                return Object.class;
            }
            return (Class) ((ParameterizedType) param).getRawType();
        }

        return (Class) params[parameterIndex];
    }

    /**
     * 获取指定class的包含泛型的类型
     */
    public static ParameterizedType getGenericType(Type genType, int parameterIndex) {
        if (!(genType instanceof ParameterizedType)) {
            return null;
        }

        Type[] params = ((ParameterizedType) genType).getActualTypeArguments();
        if (parameterIndex >= params.length || parameterIndex < 0) {
            throw new IndexOutOfBoundsException();
        }

        Type type = params[parameterIndex];
        if (!(type instanceof ParameterizedType)) {
            return null;
        }
        return (ParameterizedType) type;
    }

    /**
     * 把传入的String类型的值转换成ParameterizedType对应的对象，如果genericType为空时转换成genericClass类型
     */
    @SuppressWarnings("unchecked")
    public static Object parseObject(String result, ParameterizedType genericType, Class genericClass) {
        if (result == null) {
            return null;
        }
        if (genericType != null) {
            if (Map.class.equals(genericClass)) {
                Map sourceMap = JSON.parseObject(result, Map.class);
                ParameterizedType keyGenericType = GenericTypeUtil.getGenericType(genericType, 0);
                ParameterizedType valueGenericType = GenericTypeUtil.getGenericType(genericType, 1);
                Class keyGenericClass = GenericTypeUtil.getGenericClass(genericType, 0);
                Class valueGenericClass = GenericTypeUtil.getGenericClass(genericType, 1);
                Map resultMap = new HashMap<>(sourceMap.size());
                sourceMap.forEach((key, value) ->
                        resultMap.put(
                                parseObject(JsonUtil.toJSONString(key), keyGenericType, keyGenericClass),
                                parseObject(JsonUtil.toJSONString(value), valueGenericType, valueGenericClass)));
                return resultMap;
            } else if (List.class.equals(genericClass)) {
                List sourceList = JSON.parseObject(result, List.class);
                ParameterizedType type = GenericTypeUtil.getGenericType(genericType, 0);
                Class clazz = GenericTypeUtil.getGenericClass(genericType, 0);
                List resultList = new ArrayList<>();
                for (Object obj : sourceList) {
                    resultList.add(parseObject(JsonUtil.toJSONString(obj), type, clazz));
                }
                return resultList;
            }
            genericClass = (Class) genericType.getRawType();
        }
        return JSON.parseObject(result, genericClass);
    }

    private GenericTypeUtil() {}
}
